﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using AwesomeGame.Screens.Controls;
using AwesomeGame.UserInput;
using AwesomeGame.Networking;
using AwesomeGame.Weapons;
using AwesomeGame.Map;
using AwesomeGame.Weapons.Pickups;

namespace AwesomeGame.Screens
{
    public class GameplayScreen : IScreen
    {
        private Texture2D _background;
        private Player _localPlayer;
        private PlayerList _playerList;
        private Messages _messageList;
        private MessageSender _messageSender;
        private Client _client;
        private Server _server;
        private Tank _localTank;
        private List<Tank> _tanks = new List<Tank>();
        private List<IProjectile> _projectiles = new List<IProjectile>();
        private List<Bomb> _bombs = new List<Bomb>();
        private List<Explosion> _explosions = new List<Explosion>();
        private bool _isHosting;
        private Level _level;

        #region Constructors/Initialization

        public GameplayScreen(string playerName, string serverAddress, bool isHosting)
        {
            Camera.SetWorldSize(2048, 1536);
            _background = Game1.ContentManager.Load<Texture2D>(@"TestBackground");
            _localPlayer = new Player(-1, playerName, 0);
            _playerList = new PlayerList(new Vector2(10, 10));
            _messageList = new Messages(new Vector2(300, 10));
            _messageSender = new MessageSender(new Vector2(10, 700));
            _messageSender.SendMessage += new MessageSender.MessageEvent(MessageSenderEvent_SendMessage);
            _level = new Level(@"Content\Levels\TestLevel1.png");

            _isHosting = isHosting;

            if (isHosting)
            {
                InitializeServer(12345);
                serverAddress = "localhost";
            }

            InitializeClient(serverAddress, 12345);
        }

        private void InitializeServer(int port)
        {
            _server = new Server(port);
            _server.MessageReceived += new Server.SayEvent(NetworkEvent_MessageReceived);
            _server.Start();
        }

        private void InitializeClient(string serverAddress, int port)
        {
            _client = new Client(serverAddress, port, ref _localPlayer);
            _client.LocalPlayerIdChanged += new Client.PlayerEvent(ClientEvent_LocalPlayerIdChanged);
            _client.MessageReceived += new Client.SayEvent(NetworkEvent_MessageReceived);
            _client.PlayerListUpdated += new Client.PlayerListEvent(ClientEvent_PlayerListUpdated);
            _client.PlayerJoined += new Client.JoinEvent(ClientEvent_PlayerJoined);
            _client.TankPositionUpdated += new Client.TankPositionEvent(ClientEvent_TankPositionUpdated);
            _client.TankFired += new Client.TankFiredEvent(ClientEvent_TankFired);
            _client.PlayerWasKilled += new Client.PlayerDeathEvent(ClientEvent_PlayerWasKilled);
            _client.RemoveProjectileEvent += new Client.ProjectileEvent(ClientEvent_RemoveProjectileEvent);
            _client.BombCreated += new Client.LocationEvent(ClientEvent_BombCreated);
            _client.MoveProjectile += new Client.ProjectileMovedEvent(ClientEvent_MoveProjectile);
            _client.Start();
        }

        #endregion

        #region Client Events

        private void ClientEvent_MoveProjectile(Guid id, Vector2 position)
        {
            for (int i = _projectiles.Count - 1; i >= 0; i--)
                if (_projectiles[i].Id == id)
                {
                    _projectiles[i].Position = position;
                    break;
                }
        }

        private void ClientEvent_LocalPlayerIdChanged(long id)
        {
            _localPlayer.Id = id;
        }

        private void ClientEvent_PlayerWasKilled(long playerWhoDied, long playerWhoKilledThem)
        {
            if (playerWhoDied == playerWhoKilledThem)
                _playerList.ChangePlayerScore(playerWhoDied, -1);
            else
                _playerList.ChangePlayerScore(playerWhoKilledThem, 1);
        }

        private void ClientEvent_BombCreated(Vector2 position)
        {
            CreateBomb(position);
        }

        private void ClientEvent_RemoveProjectileEvent(Guid id)
        {
            for (int i = _projectiles.Count - 1; i >= 0; i--)
                if (_projectiles[i].Id == id)
                {
                    _projectiles.Remove(_projectiles[i]);
                    break;
                }
        }

        private void ClientEvent_TankFired(Vector2 position, float rotation, WeaponType weapon, long tankId, Guid fireEventId)
        {
            if (weapon == WeaponType.Bullet)
                CreateBullet(position, rotation, fireEventId, tankId);
            else if (weapon == WeaponType.BouncyBullet)
                CreateBouncyBullet(position, rotation, fireEventId, tankId);
            else if (weapon == WeaponType.Rocket)
                CreateRocket(position, rotation, fireEventId, tankId);

            foreach (Tank tank in _tanks)
                if (tank.Id == tankId)
                    tank.Fire();
        }

        void ClientEvent_TankPositionUpdated(Vector2 position, float rotation, long id)
        {
            foreach (Tank tank in _tanks)
                if (tank.Id == id)
                {
                    tank.Position = position;
                    tank.Rotation = rotation;
                }
        }

        void ClientEvent_PlayerJoined(Player player)
        {
            _messageList.AddMessage("Player " + player.Name + " has joined.");
            _playerList.AddPlayer(player);
            bool addingLocalTank = player.Id == _localPlayer.Id;
            Tank tank = new Tank(new Vector2(100, 100), addingLocalTank, ref _level);
            tank.Id = player.Id;
            if (addingLocalTank)
            {
                tank.TankMoved += new Tank.TankEvent(TankEvent_TankMoved);
                tank.TankFired += new Tank.TankEvent(TankEvent_TankFired);
                tank.DroppedBomb += new Tank.TankEvent(TankEvent_DroppedBomb);
                _localTank = tank;
            }
            _tanks.Add(tank);
        }

        private void ClientEvent_PlayerListUpdated(List<Player> players)
        {
            _playerList.Clear();
            foreach (Player player in players)
            {
                Tank tank = new Tank(new Vector2(100, 100), false, ref _level);
                tank.Id = player.Id;
                if (tank.Id != _localPlayer.Id)
                    _tanks.Add(tank);
                _playerList.AddPlayer(player);
            }
        }

        #endregion

        #region Network Events

        private void NetworkEvent_MessageReceived(string message)
        {
            _messageList.AddMessage(message);
        }

        #endregion

        #region Weapon Events

        private void CreateBouncyBullet(Vector2 position, float rotation, Guid id, long ownerId)
        {
            BouncyBullet bouncyBullet = new BouncyBullet(id, position, rotation, ownerId, ref _level);
            bouncyBullet.BulletExpired += new BouncyBullet.BulletEvent(BouncyBulletEvent_BulletExpired);
            _projectiles.Add(bouncyBullet);
        }

        private void CreateRocket(Vector2 position, float rotation, Guid id, long ownerId)
        {
            Rocket rocket = new Rocket(id, position, rotation, ownerId, ref _level);
            rocket.RocketExploded += new Rocket.RocketEvent(RocketEvent_RocketExploded);
            _projectiles.Add(rocket);
        }

        private void RocketEvent_RocketExploded(Rocket rocket)
        {
            CreateExplosion(rocket.Position, rocket.OwnerId);
            _projectiles.Remove(rocket);
            rocket = null;
        }

        private void BouncyBulletEvent_BulletExpired(BouncyBullet bullet)
        {
            _projectiles.Remove(bullet);
            bullet = null;
        }

        private void BombEvent_Exploded(Bomb sender)
        {
            _bombs.Remove(sender);
            CreateExplosion(sender.Position, 0);
            sender = null;
        }

        private void ExplosionEvent_Remove(Explosion sender)
        {
            _explosions.Remove(sender);
            sender = null;
        }

        private void BulletEvent_BulletExpired(Bullet bullet)
        {
            _projectiles.Remove(bullet);
            bullet = null;
        }

        #endregion

        #region Tank Events

        public void TankEvent_DroppedBomb(Vector2 position, float rotation, Tank sender)
        {
            _client.SendBombCreatedEvent(position);
            CreateBomb(position);
        }

        private void TankEvent_TankFired(Vector2 position, float rotation, Tank sender)
        {
            Guid fireEventId = Guid.NewGuid();
            _client.SendTankFireEvent(position, rotation, sender.Weapon, _localPlayer.Id, fireEventId);
            if (sender.Weapon == WeaponType.Bullet)
                CreateBullet(position, rotation, fireEventId, sender.Id);
            else if (sender.Weapon == WeaponType.BouncyBullet)
                CreateBouncyBullet(position, rotation, fireEventId, sender.Id);
            else if (sender.Weapon == WeaponType.Rocket)
                CreateRocket(position, rotation, fireEventId, sender.Id);
        }

        private void TankEvent_TankMoved(Vector2 position, float rotation, Tank sender)
        {
            _client.SendTankPosition(position, rotation, _localPlayer.Id);
        }

        #endregion

        private void CreateBullet(Vector2 position, float rotation, Guid id, long ownerId)
        {
            Bullet bullet = new Bullet(id, position, rotation, ownerId, ref _level);
            bullet.BulletExpired += new Bullet.BulletEvent(BulletEvent_BulletExpired);
            _projectiles.Add(bullet);
        }        

        private void CreateBomb(Vector2 position)
        {
            Bomb bomb = new Bomb(position);
            bomb.Exploded += new Bomb.BombEvent(BombEvent_Exploded);
            _bombs.Add(bomb);
        }

        private void CreateExplosion(Vector2 position, long ownerId)
        {
            Explosion explosion = new Explosion(position, ownerId);
            explosion.Remove += new Explosion.ExplosionEvent(ExplosionEvent_Remove);
            _explosions.Add(explosion);
        }

        private void MessageSenderEvent_SendMessage(string message)
        {
            string fullMessage = _localPlayer.Name + ": " + message;
            _client.SendMessage(fullMessage);
        }

        private void CheckForProjectileCollisions()
        {
            for (int i = _projectiles.Count - 1; i >= 0; i--)
            {
                if (_projectiles[i].IsDeadly )
                {
                    DestructableWorldItem worldItem = _level.GetDestructableItemAtWorldPosition(_projectiles[i].Position);
                    if (_localTank.PointIntersectsWithTank(_projectiles[i].Position) && _localTank._tankIsSafe < 1)
                    {
                        string ownerName = _playerList.GetPlayerNameFromId(_projectiles[i].OwnerId);
                        _client.SendMessage(_localPlayer.Name + " got hit by " + ownerName);
                        _client.SendPlayerDeath(_localPlayer.Id, _projectiles[i].OwnerId);
                        _client.SendRemoveProjectileEvent(_projectiles[i].Id);
                        RespawnLocalTank();
                        _projectiles.Remove(_projectiles[i]);
                        break;
                    }
                    else if (worldItem != null && worldItem.isDestroyed == false)
                        worldItem.Health -= 1;
                }
            }
        }

        private void RespawnLocalTank()
        {
            Vector2 respawnLocation = _level.GetRandomSpawnWorldPosition();
            _localTank.Position = respawnLocation;
            TankEvent_TankMoved(_localTank.Position, _localTank.Rotation, _localTank);
            _localTank._tankIsSafe = 100;
        }

        private void CheckForWeaponPickups()
        {
            if (_localTank != null)
            {
                foreach (WeaponPickup pickup in _level.WeaponPickups)
                    if (Common.RectanglesIntersect(_localTank.GetCollisionRectangle(), pickup.GetCollisionRectangle()))
                    {
                        _localTank.Weapon = pickup.Weapon;
                        break;
                    }
            }
        }

        private void CheckForExplosionDeaths()
        {
            if (_localTank != null)
            {
                for (int i = _explosions.Count - 1; i >= 0; i--)
                    if (Common.RectanglesIntersect(_localTank.GetCollisionRectangle(), _explosions[i].GetCollisionRectangle()))
                    {
                        string ownerName = _playerList.GetPlayerNameFromId(_explosions[i].OwnerId);
                        _client.SendMessage(_localPlayer.Name + " got hit by " + ownerName);
                        _client.SendPlayerDeath(_localPlayer.Id, _explosions[i].OwnerId);
                        RespawnLocalTank();
                        break;
                    }

            }
        }

        public void Update(GameTime gameTime)
        {
            if (_isHosting)
                _server.Update();
            _client.Update();
            _messageList.Update(gameTime);
            _playerList.Update(gameTime);
            _messageSender.Update(gameTime);
            CheckForProjectileCollisions();
            CheckForWeaponPickups();
            CheckForTeleports(gameTime);
            CheckForExplosionDeaths();
            _level.Update(gameTime);
            foreach (Tank tank in _tanks)
                tank.Update(gameTime);
            for (int i = _projectiles.Count - 1; i >= 0; i--)
                _projectiles[i].Update(gameTime);
            for (int i = _bombs.Count - 1; i >= 0; i--)
                _bombs[i].Update(gameTime);
            for (int i = _explosions.Count - 1; i >= 0; i--)
                _explosions[i].Update(gameTime);
            if (_localTank != null)
                Camera.CenterCameraOnWorldPosition(_localTank.Position.X, _localTank.Position.Y);
        }

        public void CheckForTeleports(GameTime gameTime)
        {
            if (_localTank != null)
            {
                foreach (Teleporter teleporter in _level.Teleporters)
                {
                    if (gameTime.TotalGameTime - _localTank.LastTeleport >= new TimeSpan(0, 0, 3))
                        if (Common.RectanglesIntersect(_localTank.GetCollisionRectangle(), teleporter.GetCollisionRectangle()))
                        {
                            Vector2 teleportLocation = _level.GetRandomTeleporterWorldPosition();
                            _localTank.Position = teleportLocation;
                            TankEvent_TankMoved(_localTank.Position, _localTank.Rotation, _localTank);
                            _localTank.LastTeleport = gameTime.TotalGameTime;
                            break;
                        }

                    foreach(IProjectile projectile in _projectiles)
                        if(projectile.OwnerId == _localTank.Id)
                            if (gameTime.TotalGameTime - projectile.LastTeleport >= new TimeSpan(0, 0, 3))
                                if (Common.IsPointInRectangle(projectile.Position, teleporter.GetCollisionRectangle()))
                                {
                                    Vector2 teleportLocation = _level.GetRandomTeleporterWorldPosition();
                                    _client.SendMoveProjectileEvent(projectile.Id, teleportLocation);
                                    projectile.LastTeleport = gameTime.TotalGameTime;
                                    projectile.Position = teleportLocation;                                     
                                }
                }


            }
        }

        public void Draw(GameTime gameTime)
        {
            Vector2 screenPosition = Camera.GetScreenPosition(Vector2.Zero);
            Game1.SpriteBatch.Draw(_background, screenPosition, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9f);
            _messageList.Draw(gameTime);
            _playerList.Draw(gameTime);
            _messageSender.Draw(gameTime);
            _level.Draw(gameTime);
            foreach (Tank tank in _tanks)
                tank.Draw(gameTime);
            for (int i = _projectiles.Count - 1; i >= 0; i--)
                _projectiles[i].Draw(gameTime);
            for (int i = _bombs.Count - 1; i >= 0; i--)
                _bombs[i].Draw(gameTime);
            for (int i = _explosions.Count - 1; i >= 0; i--)
                _explosions[i].Draw(gameTime);
        }

        public void Dispose()
        {
        }

    }

}
